

<!DOCTYPE html>
<html lang="zh-CN">

<head><meta name="generator" content="Hexo 3.9.0">
  <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>ViewModel的左膀右臂 数据驱动真的香 - 杨云召 | 博客</title>
  <meta name="apple-mobile-web-app-capable" content="yes">
  <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent">
  <meta name="google" content="notranslate">

  
  
  <meta name="description" content="Jetpack LiveData 介绍。


前言
之..."> 
  
  <meta name="author" content="杨云召（Flywith24）"> 

  
    <link rel="icon" href="/images/icons/favicon-16x16.png" type="image/png" sizes="16x16">
  
  
    <link rel="icon" href="/images/icons/favicon-32x32.png" type="image/png" sizes="32x32">
  
  
    <link rel="apple-touch-icon" href="/images/icons/apple-touch-icon.png" sizes="180x180">
  
  
    <meta rel="mask-icon" href="/images/icons/stun-logo.svg" color="#333333">
  
  
    <meta rel="msapplication-TileImage" content="/images/icons/favicon-144x144.png">
    <meta rel="msapplication-TileColor" content="#000000">
  

  <link rel="stylesheet" href="/css/style.css">

  
  <link rel="stylesheet" href="//at.alicdn.com/t/font_1445822_hhodbqn7tit.css">
  

  
  
  <link rel="stylesheet" href="https://cdn.bootcss.com/fancybox/3.5.7/jquery.fancybox.min.css">
  

  
  
  <link rel="stylesheet" href="https://cdn.bootcss.com/highlight.js/9.18.1/styles/xcode.min.css">
  

  <script>
    var CONFIG = window.CONFIG || {};
    var ZHAOO = window.ZHAOO || {};
    CONFIG = {
      isHome: false,
      fancybox: true,
      pjax: false,
      lazyload: {
        enable: true,
        loadingImage: '/images/theme/loading.gif',
      },
      donate: {
        enable: true,
        alipay: 'https://gitee.com/flywith24/Album/raw/master/img/20201015113301.jpg',
        wechat: 'https://gitee.com/flywith24/Album/raw/master/img/20201015112814.png'
      },
      motto: {
        api: '',
        default: '不人云亦云，只求接近真相.'
      },
      galleries: {
        enable: true
      },
      fab: {
        enable: true,
        alwaysShow: false
      },
      carrier: {
        enable: true
      },
      daovoice: {
        enable: true
      }
    }
  </script>

  

  
</head></html>
<body class="lock-screen">
  <div class="loading"></div>
  


<nav class="navbar">
  <div class="left"></div>
  <div class="center">ViewModel的左膀右臂 数据驱动真的香</div>
  <div class="right">
    <i class="iconfont iconmenu j-navbar-menu"></i>
  </div>
</nav>

  <nav class="menu">
  <div class="menu-wrap">
    <div class="menu-close">
      <i class="iconfont iconbaseline-close-px"></i>
    </div>
    <ul class="menu-content">
      
      
      
      
      <li class="menu-item"><a href="/ " class="underline"> 首页</a></li>
      
      
      
      
      <li class="menu-item"><a href="/archives " class="underline"> 归档</a></li>
      
      
      
      
      <li class="menu-item"><a href="/tags " class="underline"> 标签</a></li>
      
      
      
      
      <li class="menu-item"><a href="/categories " class="underline"> 分类</a></li>
      
      
      
      
      <li class="menu-item"><a href="/about " class="underline"> 关于</a></li>
      
    </ul>
    <div class="menu-copyright"><p>Copyright© 2017 - 2020 ❤️ Flywith24</p></div>
  </div>
</nav>
  <main id="main">
  <div class="container" id="container">
    <article class="article">
  <div class="wrap">
    <section class="head">
  <img   class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20201015101641.png" src=""  draggable="false">
  <div class="head-mask">
    <h1 class="head-title">ViewModel的左膀右臂 数据驱动真的香</h1>
    <div class="head-info">
      <span class="post-info-item"><i class="iconfont iconcalendar"></i>三月 31, 2020</span
        class="post-info-item">
      
      <span class="post-info-item"><i class="iconfont iconfont-size"></i>11046</span>
    </div>
  </div>
</section>
    <section class="main">
      <section class="content">
        <h2 id="前言"><a href="#前言" class="headerlink" title="前言"></a>前言</h2><blockquote>
<p>之前我们讨论过 <a href="https://juejin.im/post/5e786d415188255e00661a4e" target="_blank" rel="noopener">ViewModel 的职能边界</a> ，得益于 ViewModel 的生命周期更长，我们可以在 activity 重建后将数据传递给 activity ，也可以避免内存泄漏。但是如果不是每次需要就获取数据，而是当每次有新数据时通知我们，应该怎么办？</p>
</blockquote>
<p>本文介绍 <code>LiveData</code> ，一个 <strong>生命周期感知的，可观察的，数据持有者</strong>。同时还会简单分析 <code>LiveData</code> 的源码实现</p>
<h2 id="我们都是-Adapter"><a href="#我们都是-Adapter" class="headerlink" title="我们都是 Adapter"></a>我们都是 Adapter</h2><p>在谈 <code>LiveData</code> 前我们来思考一个问题</p>
<p><strong>Android 开发（亦或者说前端开发）的本质工作内容是什么？</strong></p>
<p>对于应用层 app 开发者，开发者的工作主要工作就是 Adapter </p>
<p>什么是 Adapter ，下图可能比较直观</p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200330153727.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="Adapter"></p>
<blockquote>
<p>图片来自 google image</p>
</blockquote>
<p>我们的工作本质是 <strong>将数据转换成 UI</strong> </p>
<p>数据可能来自网络，来自本地数据库，来自内存，而 UI 可能是 activity 或 fragment。</p>
<h2 id="理想的数据模型"><a href="#理想的数据模型" class="headerlink" title="理想的数据模型"></a>理想的数据模型</h2><p>上面我们提到 Android 开发者的核心工作就是将数据转换为 UI 。这个过程比较理想的状态是：当数据发生变化时，UI 跟随变化。我们还可以进一步展开：当 UI 对用户可见时，数据发生变化时 UI 跟随变化；当 UI 对用户不可见时，我们希望数据变化时什么都不做，当 UI 再次对用户可见时根据最新的数据进行 UI 的处理。</p>
<p>而 <code>LiveData</code> 就是我们理想中的数据模型</p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200330160205.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="LiveData"></p>
<blockquote>
<p>图片来自 <a href="https://www.youtube.com/watch?v=2rO4r-JOQtA&list=PLWz5rJ2EKKc_dskHzXdKHB2ZvAlMyFwZe&index=2&t=0s" target="_blank" rel="noopener">Android Dev Summit ‘18-Fun with LiveData</a></p>
</blockquote>
<p> LiveData 可以三个关键词概括</p>
<ul>
<li><p>lifecycle-aware</p>
</li>
<li><p>observable</p>
</li>
<li><p>data holder</p>
</li>
</ul>
<h3 id="observable"><a href="#observable" class="headerlink" title="observable"></a>observable</h3><p>Android 中不同的组件有着不同的生命周期，不同的存活时间</p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200321140641.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="ViewModel"></p>
<p>因此我们不会在 <code>ViewModel</code> 中持有 <code>Activity</code> 的引用，因为这会导致当 <code>Activity</code> 重建时内存泄漏，甚至出现空指针的情况</p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200330161100.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="observable"></p>
<p>通常我们会在 <code>Activity</code> 中持有 <code>ViewModel</code> 的引用，那么如何进行二者间的通信，如何向 <code>Activity</code> 发送 <code>ViewModel</code> 中的数据？</p>
<p>答案是让 <code>Activity</code> 观察 <code>ViewModel</code></p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200330161411.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p><code>LiveData</code> 是 <code>observable</code></p>
<h3 id="lifecycle-aware"><a href="#lifecycle-aware" class="headerlink" title="lifecycle-aware"></a>lifecycle-aware</h3><p>当观察者观察着某个数据时，该数据必须保留对观察者的引用才能调用它，为了解决这个问题，<code>LiveData</code> 被设计成可感知生命周期</p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200330162126.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>当 activity / fragment 被销毁后，它会自动的取消订阅</p>
<h3 id="data-holder"><a href="#data-holder" class="headerlink" title="data holder"></a>data holder</h3><p><code>LiveData</code> 仅持有 <strong>单个且最新</strong> 的数据</p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200330214220.gif" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="data holder"></p>
<p>上图中，最右侧是在 <code>ViewModel</code> 中的 <code>LiveData</code>，左侧为观察这个 <code>LiveData</code> 的 activity / fragment 。一旦我们为 <code>LiveData</code> 设值，该值会传递到 activity。简而言之，<code>LiveData</code> 值改变，activity 收到最新的值的变化。但是当观察者不再处于活动状态（STARTED 到 RESUMED ），数据 C 不会被发送到 activity 。当 activity 回到前台，它将收到最新的值，数据 D。<strong>LiveData 仅持有单个且最新的数据</strong>。当 activity 执行销毁流程时，此时的数据 E 也不会产生任何影响</p>
<h2 id="Transformations"><a href="#Transformations" class="headerlink" title="Transformations"></a>Transformations</h2><p><code>LiveData</code> 提供 两种 transformation ，<code>map</code> 和 <code>switch map</code>。开发者也可以创建自定义的  <code>MediatorLiveData</code> </p>
<p>我们都知道 <code>LiveData</code> 可以为 View 和 ViewModel 提供通信，但如果有一个第三方组件（例如 repository ）也持有 <code>LiveData</code>。那么它应该如何在 <code>ViewModel</code> 中订阅？该组件并没有 lifecycle </p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200331090310.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>一旦我们的应用愈发复杂，repository 可能会观察数据源</p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200331090412.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>那么 view 如何获取 repository 中的 <code>LiveData</code>？</p>
<h3 id="一对一的静态转换（map）"><a href="#一对一的静态转换（map）" class="headerlink" title="一对一的静态转换（map）"></a>一对一的静态转换（map）</h3><p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200331090941.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="one-to-one static transformation"></p>
<p>在上面的示例中，<code>ViewModel</code> 仅将数据从 repository 转发到 view，然后将其转换为 UI Model。 每当 repository 中有新数据时，<code>ViewModel</code> 只需 <code>map</code> </p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainViewModel</span> </span>&#123;<br>  <span class="hljs-keyword">val</span> viewModelResult = Transformations.map(repository.getDataForUser()) &#123; <span class="hljs-keyword">data</span> -&gt;<br>     convertDataToMainUIModel(<span class="hljs-keyword">data</span>)<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>第一个参数为 <code>LiveData</code> 源（来自 repository ），第二个参数是一个转换函数。</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-comment">// 这里的转换为将 X 转换为 Y</span><br><span class="hljs-keyword">inline</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;X, Y&gt;</span> LiveData<span class="hljs-type">&lt;X&gt;</span>.<span class="hljs-title">map</span><span class="hljs-params">(<span class="hljs-keyword">crossinline</span> transform: (<span class="hljs-type">X</span>)</span></span> -&gt; Y): LiveData&lt;Y&gt; =<br>        Transformations.map(<span class="hljs-keyword">this</span>) &#123; transform(it) &#125;<br></code></pre></td></tr></table></figure>

<h3 id="一对一的动态转换（switchMap）"><a href="#一对一的动态转换（switchMap）" class="headerlink" title="一对一的动态转换（switchMap）"></a>一对一的动态转换（switchMap）</h3><p>假如您正在观察一个提供用户的用户管理器，并且需要提供用户的 id 才能开始观察 repository </p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200331091954.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>您不能将其写到 <code>ViewModel</code> 初始化的过程中，因为此时用户的 id 还不可用</p>
<p>这时 <code>switchMap</code> 就派上用场了</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">MainViewModel</span> </span>&#123;<br>  <span class="hljs-keyword">val</span> repositoryResult = Transformations.switchMap(userManager.userId) &#123; userId -&gt;<br>     repository.getDataForUser(userId)<br>  &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p><code>switchMap</code> 在内部使用 <code>MediatorLiveData</code>，因此了解它非常重要，因为当您要组合多个 <code>LiveData</code> 源时需要使用它</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-comment">// 这里的转换为将 X 转换为 LiveData&lt;Y&gt;</span><br><span class="hljs-keyword">inline</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-type">&lt;X, Y&gt;</span> LiveData<span class="hljs-type">&lt;X&gt;</span>.<span class="hljs-title">switchMap</span><span class="hljs-params">(<br>    <span class="hljs-keyword">crossinline</span> transform: (<span class="hljs-type">X</span>)</span></span> -&gt; LiveData&lt;Y&gt;<br>): LiveData&lt;Y&gt; = Transformations.switchMap(<span class="hljs-keyword">this</span>) &#123; transform(it) &#125;<br></code></pre></td></tr></table></figure>

<h3 id="一对多依赖（MediatorLiveData）"><a href="#一对多依赖（MediatorLiveData）" class="headerlink" title="一对多依赖（MediatorLiveData）"></a>一对多依赖（MediatorLiveData）</h3><p><code>MediatorLiveData</code> 允许您将一个或多个数据源添加到单个可观察的 <code>LiveData</code> 中</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">val</span> liveData1: LiveData&lt;<span class="hljs-built_in">Int</span>&gt; = ...<br><span class="hljs-keyword">val</span> liveData2: LiveData&lt;<span class="hljs-built_in">Int</span>&gt; = ...<br><br><span class="hljs-keyword">val</span> result = MediatorLiveData&lt;<span class="hljs-built_in">Int</span>&gt;()<br><br>result.addSource(liveData1) &#123; value -&gt;<br>    result.setValue(value)<br>&#125;<br>result.addSource(liveData2) &#123; value -&gt;<br>    result.setValue(value)<br>&#125;<br></code></pre></td></tr></table></figure>

<p>在上面的例子中，当任何一个数据源变化时，result 会更新。</p>
<blockquote>
<p>注意：数据并不是合并，MediatorLiveData 只是处理通知</p>
</blockquote>
<p>为了实现示例中的转换，我们需要将两个不同的 LiveData 组合为一个</p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200331094815.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<blockquote>
<p>图片来自 <a href="https://medium.com/androiddevelopers/livedata-beyond-the-viewmodel-reactive-patterns-using-transformations-and-mediatorlivedata-fda520ba00b7" target="_blank" rel="noopener">LiveData beyond the ViewModel — Reactive patterns using Transformations and MediatorLiveData</a></p>
</blockquote>
<p>使用 <code>MediatorLiveData</code> 合并数据的一种方法是添加源并以其他方法设置值：</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">blogpostBoilerplateExample</span><span class="hljs-params">(newUser: <span class="hljs-type">String</span>)</span></span>: LiveData&lt;UserDataResult&gt; &#123;<br><br>    <span class="hljs-keyword">val</span> liveData1 = userOnlineDataSource.getOnlineTime(newUser)<br>    <span class="hljs-keyword">val</span> liveData2 = userCheckinsDataSource.getCheckins(newUser)<br><br>    <span class="hljs-keyword">val</span> result = MediatorLiveData&lt;UserDataResult&gt;()<br><br>    result.addSource(liveData1) &#123; value -&gt;<br>        result.value = combineLatestData(liveData1, liveData2)<br>    &#125;<br>    result.addSource(liveData2) &#123; value -&gt;<br>        result.value = combineLatestData(liveData1, liveData2)<br>    &#125;<br>    <span class="hljs-keyword">return</span> result<br>&#125;<br></code></pre></td></tr></table></figure>

<p>数据的实际组合是在 <code>combineLatestData</code> 方法中完成的</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">private</span> <span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">combineLatestData</span><span class="hljs-params">(<br>        onlineTimeResult: <span class="hljs-type">LiveData</span>&lt;<span class="hljs-type">Long</span>&gt;,<br>        checkinsResult: <span class="hljs-type">LiveData</span>&lt;<span class="hljs-type">CheckinsResult</span>&gt;<br>)</span></span>: UserDataResult &#123;<br><br>    <span class="hljs-keyword">val</span> onlineTime = onlineTimeResult.value<br>    <span class="hljs-keyword">val</span> checkins = checkinsResult.value<br><br>    <span class="hljs-comment">// Don't send a success until we have both results</span><br>    <span class="hljs-keyword">if</span> (onlineTime == <span class="hljs-literal">null</span> || checkins == <span class="hljs-literal">null</span>) &#123;<br>        <span class="hljs-keyword">return</span> UserDataLoading()<br>    &#125;<br><br>    <span class="hljs-comment">// <span class="hljs-doctag">TODO:</span> Check for errors and return UserDataError if any.</span><br><br>    <span class="hljs-keyword">return</span> UserDataSuccess(timeOnline = onlineTime, checkins = checkins)<br>&#125;<br></code></pre></td></tr></table></figure>

<p>检查值是否准备好并发出结果（加载中，失败或成功）</p>
<h2 id="LiveData-的错误用法"><a href="#LiveData-的错误用法" class="headerlink" title="LiveData 的错误用法"></a>LiveData 的错误用法</h2><h3 id="错误地使用-var-LiveData"><a href="#错误地使用-var-LiveData" class="headerlink" title="错误地使用 var LiveData"></a>错误地使用 var LiveData</h3><figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-keyword">var</span> <span class="hljs-keyword">lateinit</span> randomNumber: LiveData&lt;<span class="hljs-built_in">Int</span>&gt;<br><br><span class="hljs-function"><span class="hljs-keyword">fun</span> <span class="hljs-title">onGetNumber</span><span class="hljs-params">()</span></span> &#123;<br>   randomNumber = Transformations.map(numberGenerator.getNumber()) &#123;<br>       it<br>   &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>这里有一个重要的问题需要理解：转换会在调用时（<code>map</code> 和 <code>switchMap</code>）会创建一个新的 <code>LiveData</code>。 在此示例中，randomNumber 公开给 View ，但是每次用户单击按钮时都会对其进行重新赋值。 观察者只会在订阅时收到分配给 var 的 <code>LiveData</code> 更新的信息</p>
<figure class="highlight kotlin"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br></pre></td><td class="code"><pre><code class="hljs kotlin"><span class="hljs-comment">// 只会收到第一次分配的值</span><br>viewmodel.randomNumber.observe(<span class="hljs-keyword">this</span>, Observer &#123; number -&gt;<br>    numberTv.text = resources.getString(R.string.random_text, number)<br>&#125;)<br></code></pre></td></tr></table></figure>

<p>如果 viewmodel.randomNumber <code>LiveData</code> 实例发生更改，这里永远不会回调。而且这里泄漏了之前的 <code>LiveData</code> ，这些 <code>LiveData</code> 不会再发送更新</p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200331111630.gif" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>一言以蔽之，<strong>不要在 var 中使用 Livedata</strong></p>
<p>正确示例见  <a href="https://github.com/Flywith24/Flywith24-Jetpack-Demo/tree/master/demo_livedata" target="_blank" rel="noopener">demo</a></p>
<h3 id="LiveData-粘性事件"><a href="#LiveData-粘性事件" class="headerlink" title="LiveData 粘性事件"></a>LiveData 粘性事件</h3><p>一般来说我们使用 LiveData 持有 UI 数据和状态，但是如果通过它来发送事件，可能会出现一些问题。这些问题及解决方案 <a href="https://juejin.im/post/5b2b1b2cf265da5952314b63" target="_blank" rel="noopener">在这</a></p>
<h3 id="fragment-中错误地传入-LifecycleOwner"><a href="#fragment-中错误地传入-LifecycleOwner" class="headerlink" title="fragment 中错误地传入 LifecycleOwner"></a>fragment 中错误地传入 LifecycleOwner</h3><p><code>androidx fragment 1.2.0</code> 起，添加了新的 Lint 检查，以确保您在从 onCreateView()、onViewCreated() 或 onActivityCreated() 观察 <code>LiveData</code> 时使用 getViewLifecycleOwner()</p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200331152505.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200331205402.gif" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt="bug"></p>
<p>如图，我们有一个 fragment ，onCreate 观察 <code>LiveData</code>，通过正常的生命周期创建了 View ，接着进入了 resume 状态。此时你使用了 <code>LiveData</code>，UI 将开始展示它。之后，用户点击了按钮，由于跳转了另一个 fragment，所以要 detach 该 fragment，一旦 fragment stop 我们就不需要其中的 view 了，因此 destroyView 。之后用户点击了返回按钮回到了上一个 fragment，由于我们已经 destroyView，因此我们需要创建一个新的 view ，接着进入正常的生命周期，但此时，出现了一个 bug 。这个新 View 不会恢复 <code>LiveData</code> 的状态，因为我们使用的是 fragment 的 lifecycle observe 的 <code>LiveData</code></p>
<p>我们有两种选择，在 onCreate 或者在 onCreateView 中使用 fragment 的 lifecycle observe LiveData</p>
<p><img class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20200331171844.png" src="/2020/03/31/Jetpack-LiveData/gif;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABCAYAAAAfFcSJAAAADUlEQVQImWNgYGBgAAAABQABh6FO1AAAAABJRU5ErkJggg==" alt></p>
<p>前者的优点是一次注册，缺点是当 recreate 时有bug；后者优点是能够解决 recreate 的 bug，但会导致重复注册</p>
<p><strong>该问题的核心是 fragment 拥有两个生命周期：fragment 自身和 fragment 内部 view 的生命周期</strong></p>
<p><code>androidx fragment 1.0</code> 和 <code>support library 28</code> 了 viewLifecycle</p>
<p><strong>因此，当需要观察 view 相关的 LiveData ，可以在 onCreateView()、onViewCreated() 或 onActivityCreated()  中 LiveData observe 方法中传入 viewLifecycleOwner 而不是传入 this</strong></p>
<h2 id="源码结构"><a href="#源码结构" class="headerlink" title="源码结构"></a>源码结构</h2><p>首先来看 <code>LiveData</code> 主要的源码结构</p>
<ul>
<li>LiveData </li>
<li>MutableLiveData</li>
<li>Observer</li>
</ul>
<h3 id="LiveData"><a href="#LiveData" class="headerlink" title="LiveData"></a>LiveData</h3><p><code>LiveData</code> 是可以在给定生命周期内观察到的数据持有者类。 这意味着可以将一个<code>Observer</code> 与 <code>LifecycleOwner</code> 成对添加，并且只有在配对的 <code>LifecycleOwner</code> 处于活动状态时，才会向该观察者通知有关包装数据的修改。 如果 LifecycleOwner 的状态为 <code>Lifecycle.State.STARTED</code> 或 <code>Lifecycle.State.RESUMED</code>，则将其视为活动状态。 通过 <code>observeForever</code>（Observer）添加的观察者被视为始终处于活动状态，因此将始终收到有关修改的通知。 对于那些观察者，需要手动调用 <code>removeObserver</code>（Observer）</p>
<p>如果相应的生命周期移至 <code>Lifecycle.State.DESTROYED</code> 状态，则添加了生命周期的观察者将被自动删除。 这对于 activity 和 fragment 可以安全地观察 <code>LiveData</code> 而不用担心泄漏</p>
<p>此外，<code>LiveData</code> 具有 onActive() 和 onInactive() 方法，以便在活动观察者的数量在 0 到 1 之间变化时得到通知。这使 <code>LiveData</code> 在没有任何活动观察者的情况下可以释放大量资源。</p>
<p>主要方法有：</p>
<ul>
<li>T getValue() 获取LiveData 包装的数据</li>
<li>observe(LifecycleOwner owner, Observer&lt;? super T&gt; observer) 设置观察者（主线程调用）</li>
<li>setValue(T value)  设值（主线程调用），可见性为 protected 无法直接使用</li>
<li>postValue(T value) 设置（其他线程调用），可见性为 protected 无法直接使用</li>
</ul>
<h3 id="MutableLiveData"><a href="#MutableLiveData" class="headerlink" title="MutableLiveData"></a>MutableLiveData</h3><p><code>LiveData</code> 实现类，公开了 <code>setValue</code> 和 <code>postValue</code> 方法</p>
<h3 id="Observer"><a href="#Observer" class="headerlink" title="Observer"></a>Observer</h3><p>接口，内部只有 onChanged(T t) 方法，在数据变化时该方法会被调用</p>
<h2 id="源码分析"><a href="#源码分析" class="headerlink" title="源码分析"></a>源码分析</h2><p>我们通过源码来看看 <code>LiveData</code> 如何实现它的特性的</p>
<ul>
<li><ol>
<li>如何控制在 activity 或 fragment 活动状态时接收回调，否则不接收？</li>
</ol>
</li>
<li><ol start="2">
<li>如何在 activity 或 fragment 销毁时自动取消注册观察者？</li>
</ol>
</li>
<li><ol start="3">
<li>如何保证 <code>LiveData</code> 持有最新的数据？</li>
</ol>
</li>
</ul>
<p>我们查看 <code>LiveData</code> 的 observe 方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br><span class="line">27</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// LiveData.java</span><br><span class="hljs-meta">@MainThread</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">observe</span><span class="hljs-params">(LifecycleOwner owner, Observer&lt;? <span class="hljs-keyword">super</span> T&gt; observer)</span> </span>&#123;<br>    <span class="hljs-keyword">if</span> (owner.getLifecycle().getCurrentState() == DESTROYED) &#123;<br>        <span class="hljs-comment">// 如果 owner 已经是 DESTROYED 状态，则忽略</span><br>        <span class="hljs-keyword">return</span>;<br>    &#125;<br>    <span class="hljs-comment">// 使用 LifecycleBoundObserver 包装 owner 和 observer</span><br>    LifecycleBoundObserver wrapper = <span class="hljs-keyword">new</span> LifecycleBoundObserver(owner, observer);<br>    ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);<br>    <span class="hljs-comment">// 如果已经添加过直接 return </span><br>    <span class="hljs-keyword">if</span> (existing != <span class="hljs-keyword">null</span>) &#123;<br>        <span class="hljs-keyword">return</span>;<br>    &#125;<br>   <br>    owner.getLifecycle().addObserver(wrapper);<br>&#125;<br><br><span class="hljs-comment">// LifecycleBoundObserver.java</span><br><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">LifecycleBoundObserver</span> <span class="hljs-keyword">extends</span> <span class="hljs-title">ObserverWrapper</span> <span class="hljs-keyword">implements</span> <span class="hljs-title">LifecycleEventObserver</span> </span>&#123;<br>    <span class="hljs-meta">@NonNull</span><br>    <span class="hljs-keyword">final</span> LifecycleOwner mOwner;<br>    LifecycleBoundObserver(LifecycleOwner owner, Observer&lt;? <span class="hljs-keyword">super</span> T&gt; observer) &#123;<br>        <span class="hljs-keyword">super</span>(observer);<br>        mOwner = owner;<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>通过源码我们知道，当我们调用 observe 方法时，内部是通过 <code>LifecycleBoundObserver</code> 将 owner 和 observer 包裹起来并通过 <code>addObserver</code> 方法添加观察者的，因而当数据变化时，会调用 <code>LifecycleBoundObserver</code> 的 <code>onStateChanged</code> 方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-comment">// LiveData.LifecycleBoundObserver.java</span><br><span class="hljs-meta">@Override</span><br><span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">void</span> <span class="hljs-title">onStateChanged</span><span class="hljs-params">(@NonNull LifecycleOwner source,<br>        @NonNull Lifecycle.Event event)</span> </span>&#123;<br>    <span class="hljs-keyword">if</span> (mOwner.getLifecycle().getCurrentState() == DESTROYED) &#123;<br>        <span class="hljs-comment">// 自动移除观察者，问题 2 得到解释</span><br>        removeObserver(mObserver);<br>        <span class="hljs-keyword">return</span>;<br>    &#125;<br>    activeStateChanged(shouldBeActive());<br>&#125;<br></code></pre></td></tr></table></figure>

<p>当什么周期所有者处于 <code>DESTROYED</code> 状态时，会调用 <code>removeObserver</code> 方法，因此问题 2  得到解释</p>
<p>我们继续向下看，<code>activeStateChanged</code> 方法调用时传入了 <code>shouldBeActive()</code></p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br><span class="line">18</span><br><span class="line">19</span><br><span class="line">20</span><br><span class="line">21</span><br><span class="line">22</span><br><span class="line">23</span><br><span class="line">24</span><br><span class="line">25</span><br><span class="line">26</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@Override</span><br><span class="hljs-function"><span class="hljs-keyword">boolean</span> <span class="hljs-title">shouldBeActive</span><span class="hljs-params">()</span> </span>&#123;<br>    <span class="hljs-comment">// 至少是 STARTED 状态 返回 true</span><br>    <span class="hljs-keyword">return</span> mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);<br>&#125;<br><br><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">activeStateChanged</span><span class="hljs-params">(<span class="hljs-keyword">boolean</span> newActive)</span> </span>&#123;<br>    <span class="hljs-keyword">if</span> (newActive == mActive) &#123;<br>        <span class="hljs-comment">// 与上次值相同，则直接 return （两次均为活动状态或均为非活动状态）</span><br>        <span class="hljs-keyword">return</span>;<br>    &#125;<br>    mActive = newActive;<br>    <span class="hljs-keyword">boolean</span> wasInactive = LiveData.<span class="hljs-keyword">this</span>.mActiveCount == <span class="hljs-number">0</span>;<br>    <span class="hljs-comment">// 根据 mActive 修改活动状态观察者的数量（加 1 或减 1 ）</span><br>    LiveData.<span class="hljs-keyword">this</span>.mActiveCount += mActive ? <span class="hljs-number">1</span> : -<span class="hljs-number">1</span>;<br>    <span class="hljs-keyword">if</span> (wasInactive &amp;&amp; mActive) &#123;<br>        onActive();<br>    &#125;<br>    <span class="hljs-keyword">if</span> (LiveData.<span class="hljs-keyword">this</span>.mActiveCount == <span class="hljs-number">0</span> &amp;&amp; !mActive) &#123;<br>        onInactive();<br>    &#125;<br>    <span class="hljs-keyword">if</span> (mActive) &#123;<br>        <span class="hljs-comment">// 如果是活动状态，则发送数据，问题 1 得到解释</span><br>        dispatchingValue(<span class="hljs-keyword">this</span>);<br>    &#125;<br>&#125;<br></code></pre></td></tr></table></figure>

<p>这里牵扯了 <code>Lifecycle State</code> 比较的知识，<a href="https://juejin.im/post/5e8348bef265da47e02a6ce2#heading-11" target="_blank" rel="noopener">详情在这</a></p>
<p>只有 <code>STARTED</code> 和 <code>RESUMED</code> 状态 <code>shouldBeActive()</code> 才返回 true，至此问题 1 得到解释</p>
<p><code>dispatchingValue</code> 方法内部调用了 <code>considerNotify</code> 方法</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br><span class="line">8</span><br><span class="line">9</span><br><span class="line">10</span><br><span class="line">11</span><br><span class="line">12</span><br><span class="line">13</span><br><span class="line">14</span><br><span class="line">15</span><br><span class="line">16</span><br><span class="line">17</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-function"><span class="hljs-keyword">private</span> <span class="hljs-keyword">void</span> <span class="hljs-title">considerNotify</span><span class="hljs-params">(ObserverWrapper observer)</span> </span>&#123;<br>    <span class="hljs-keyword">if</span> (!observer.mActive) &#123;<br>        <span class="hljs-keyword">return</span>;<br>    &#125;<br>    <span class="hljs-comment">// 再次判断生命周期所有者状态</span><br>    <span class="hljs-keyword">if</span> (!observer.shouldBeActive()) &#123;<br>        observer.activeStateChanged(<span class="hljs-keyword">false</span>);<br>        <span class="hljs-keyword">return</span>;<br>    &#125;<br>    <span class="hljs-comment">// 比较版本号</span><br>    <span class="hljs-keyword">if</span> (observer.mLastVersion &gt;= mVersion) &#123;<br>        <span class="hljs-keyword">return</span>;<br>    &#125;<br>    observer.mLastVersion = mVersion;<br>    <span class="hljs-comment">// 调用我们传入的 mObserver 的 onChanged 方法</span><br>    observer.mObserver.onChanged((T) mData);<br>&#125;<br></code></pre></td></tr></table></figure>

<p>可以看到 <code>considerNotify</code> 中比较了 observer 的版本号，如果是最新的数据，直接 return</p>
<p>而 <code>mVersion</code> 在 <code>setValue</code> 方法中 进行更改</p>
<figure class="highlight java"><table><tr><td class="gutter"><pre><span class="line">1</span><br><span class="line">2</span><br><span class="line">3</span><br><span class="line">4</span><br><span class="line">5</span><br><span class="line">6</span><br><span class="line">7</span><br></pre></td><td class="code"><pre><code class="hljs java"><span class="hljs-meta">@MainThread</span><br><span class="hljs-function"><span class="hljs-keyword">protected</span> <span class="hljs-keyword">void</span> <span class="hljs-title">setValue</span><span class="hljs-params">(T value)</span> </span>&#123;<br>    <span class="hljs-comment">// 每次设置对 mVersion 进行++</span><br>    mVersion++;<br>    mData = value;<br>    dispatchingValue(<span class="hljs-keyword">null</span>);<br>&#125;<br></code></pre></td></tr></table></figure>

<p>因此 <code>LiveData</code> 每次都持有最新的数据，问题 3 得到解释</p>
<h2 id="总结"><a href="#总结" class="headerlink" title="总结"></a>总结</h2><p>回到本文开头的思考，Android 开发者的主要工作是将数据转换成 UI ，而 <code>LiveData</code> 本质上是一种「数据驱动」，即通过改变状态数据，来驱动视图树中绑定了相应状态数据的控件重新发生绘制。Flutter 和未来的  Jetpack Compose 采用的都是这种机制。使用 ViewModel + LiveData，可以 <strong>安全地在订阅者的生命周期内分发正确的数据</strong>，使开发者不知不觉中完成了 <code>UI -&gt; ViewModel -&gt; Data</code> 的单向依赖。</p>
<p><strong>所谓架构，很多时候不是使用它能做什么，更多的是不要做什么，使用它时开发者能够得到约束，以便产出更健壮的代码</strong></p>
<p>各位小伙伴如果有什么想法欢迎在评论区留言</p>
<hr>
<h2 id="关于我"><a href="#关于我" class="headerlink" title="关于我"></a>关于我</h2><hr>
<p>我是 <a href="https://flywith24.gitee.io/">Fly_with24</a></p>
<ul>
<li><a href="https://juejin.im/user/57c7f6870a2b58006b1cfd6c" target="_blank" rel="noopener">掘金</a></li>
<li><a href="https://www.jianshu.com/u/3d5ad6043d66" target="_blank" rel="noopener">简书</a></li>
<li><a href="https://github.com/Flywith24" target="_blank" rel="noopener">Github</a></li>
</ul>
      </section>
      <section class="extra">
        
        <ul class="copyright">
  
  <li><strong>本文作者：</strong>杨云召（Flywith24）</li>
  <li><strong>本文链接：</strong><a href="https://flywith24.gitee.io/2020/03/31/Jetpack-LiveData/index.html">https://flywith24.gitee.io/2020/03/31/Jetpack-LiveData/index.html</a></li>
  <li><strong>版权声明：</strong>本博客所有文章均采用<a href="https://creativecommons.org/licenses/by-nc-sa/4.0/deed.zh"
      rel="external nofollow" target="_blank"> BY-NC-SA </a>许可协议，转载请注明出处！</li>
  
</ul>
        
        
        <section class="donate">
  <div class="qrcode">
    <img   class="lazyload" data-original="https://gitee.com/flywith24/Album/raw/master/img/20201015113301.jpg" src="" >
  </div>
  <div class="icon">
    <a href="javascript:;" id="alipay"><i class="iconfont iconalipay"></i></a>
    <a href="javascript:;" id="wechat"><i class="iconfont iconwechat-fill"></i></a>
  </div>
</section>
        
        
  <ul class="tag-list"><li class="tag-list-item"><a class="tag-list-link" href="/tags/Jetpack/">Jetpack</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/MVVM/">MVVM</a></li><li class="tag-list-item"><a class="tag-list-link" href="/tags/androidx/">androidx</a></li></ul>

        
<nav class="nav">
  
    <a href="/2020/04/09/Jetpack-DataBinding/"><i class="iconfont iconleft"></i>数据驱动魔法师 何时迎来翻身日？</a>
  
  
    <a href="/2020/03/30/Jetpack-Lifecycle/">万物基于Lifecycle 默默无闻大作用<i class="iconfont iconright"></i></a>
  
</nav>

      </section>
      
      <section class="comments">
  
  <div class="btn" id="comments-btn">查看评论</div>
  
  
<div id="valine"></div>
<script defer src="//unpkg.com/valine/dist/Valine.min.js"></script>
<script>
  window.onload = function () {
    var loadValine = function () {
      new Valine({
        el: '#valine',
        app_id: "SHA3gzCcxFlqaPcGRnX4UgET-gzGzoHsz",
        app_key: "OYo7ITS4AzTvBUvb1yIY8oaf",
        placeholder: "期待你的留言",
        avatar: "mp",
        pageSize: "10",
        lang: "zh-CN",
      });
    }
    if ( true ) {
      $("#comments-btn").on("click", function () {
        $(this).hide();
        loadValine();
      });
    } else {
      loadValine();
    }
  };
</script>

</section>
      
    </section>
  </div>
</article>
  </div>
</main>
  <footer class="footer">
  <div class="footer-social">
    
    
    
    
    
    <a href="tencent://message/?Menu=yes&uin=1032367864 " target="_blank" onMouseOver="this.style.color= '#12B7F5'"
      onMouseOut="this.style.color='#33333D'">
      <i class="iconfont footer-social-item  iconQQ "></i>
    </a>
    
    
    
    
    
    <a href="javascript:; " target="_blank" onMouseOver="this.style.color= '#8bc34a'"
      onMouseOut="this.style.color='#33333D'">
      <i class="iconfont footer-social-item  iconwechat-fill "></i>
    </a>
    
    
    
    
    
    <a href="https://flywith24.gitee.io/about/ " target="_blank" onMouseOver="this.style.color= '#d32f2f'"
      onMouseOut="this.style.color='#33333D'">
      <i class="iconfont footer-social-item  iconheart "></i>
    </a>
    
    
    
    
    
    <a href="https://github.com/Flywith24 " target="_blank" onMouseOver="this.style.color= '#24292E'"
      onMouseOut="this.style.color='#33333D'">
      <i class="iconfont footer-social-item  icongithub-fill "></i>
    </a>
    
    
    
    
    
    <a href="mailto:youngyunzhao@163.com " target="_blank" onMouseOver="this.style.color='#FFBE5B'"
      onMouseOut="this.style.color='#33333D'">
      <i class="iconfont footer-social-item  iconmail"></i>
    </a>
    
  </div>
  <div class="footer-copyright"><p>Copyright© 2017 - 2020 ❤️ Flywith24</p></div>
</footer>
  
      <div class="fab fab-plus">
    <i class="iconfont iconplus"></i>
  </div>
  
  <div class="fab fab-daovoice">
    <i class="iconfont iconcomment"></i>
  </div>
  
  <div class="fab fab-up">
    <i class="iconfont iconcaret-up"></i>
  </div>
  
<script src="/live2dw/lib/L2Dwidget.min.js?094cbace49a39548bed64abff5988b05"></script><script>L2Dwidget.init({"pluginRootPath":"live2dw/","pluginJsPath":"lib/","pluginModelPath":"assets/","tagMode":false,"debug":false,"model":{"jsonPath":"/live2dw/assets/shizuku.model.json"},"display":{"position":"left","width":150,"height":300},"mobile":{"show":"tfalse"},"log":false});</script></body>

<script src="https://cdn.bootcss.com/jquery/3.4.1/jquery.min.js"></script>




<script src="https://cdn.bootcdn.net/ajax/libs/jquery.lazyload/1.9.1/jquery.lazyload.min.js"></script>




<script src="https://cdn.bootcss.com/fancybox/3.5.7/jquery.fancybox.min.js"></script>




<script src="/js/utils.js"></script>
<script src="/js/modules.js"></script>
<script src="/js/zui.js"></script>
<script src="/js/script.js"></script>




<script>
  (function (i, s, o, g, r, a, m) {
    i["DaoVoiceObject"] = r;
    i[r] = i[r] || function () {
      (i[r].q = i[r].q || []).push(arguments)
    }, i[r].l = 1 * new Date();
    a = s.createElement(o), m = s.getElementsByTagName(o)[0];
    a.async = 1;
    a.src = g;
    a.charset = "utf-8";
    m.parentNode.insertBefore(a, m)
  })(window, document, "script", ('https:' == document.location.protocol ? 'https:' : 'http:') +
    "//widget.daovoice.io/widget/0f81ff2f.js", "daovoice")
  daovoice('init', {
    app_id: "7785620b"
  }, {
    launcher: {
      disableLauncherIcon: true,
    },
  });
  daovoice('update');
</script>



<script>
  (function () {
    var bp = document.createElement('script');
    var curProtocol = window.location.protocol.split(':')[0];
    if (curProtocol === 'https') {
      bp.src = 'https://zz.bdstatic.com/linksubmit/push.js';
    } else {
      bp.src = 'http://push.zhanzhang.baidu.com/push.js';
    }
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(bp, s);
  })();
</script>


<script>
  var _hmt = _hmt || [];
  (function () {
    var hm = document.createElement("script");
    hm.src = "https://hm.baidu.com/hm.js?4c204d8bc027a0455b5fc642ac334ca8";
    var s = document.getElementsByTagName("script")[0];
    s.parentNode.insertBefore(hm, s);
  })();
</script>










</html>